/*===========================================================================
  Copyright (C) 2014 by the Okapi Framework contributors
-----------------------------------------------------------------------------
  This library is free software; you can redistribute it and/or modify it 
  under the terms of the GNU Lesser General Public License as published by 
  the Free Software Foundation; either version 2.1 of the License, or (at 
  your option) any later version.

  This library is distributed in the hope that it will be useful, but 
  WITHOUT ANY WARRANTY; without even the implied warranty of 
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser 
  General Public License for more details.

  You should have received a copy of the GNU Lesser General Public License 
  along with this library; if not, write to the Free Software Foundation, 
  Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA

  See also the full LGPL text here: http://www.gnu.org/copyleft/lesser.html
===========================================================================*/

package net.sf.okapi.lib.xliff2.its;

import java.util.Iterator;
import java.util.concurrent.ConcurrentHashMap;

import net.sf.okapi.lib.xliff2.InvalidParameterException;
import net.sf.okapi.lib.xliff2.Util;

/**
 * Represents a collection of {@link IITSItem} objects.
 */
public class ITSItems implements Iterable<IITSItem> {

	private final ConcurrentHashMap<String, IITSItem> map = new ConcurrentHashMap<>();

	/**
	 * Creates an empty {@link ITSItems} object.
	 */
	public ITSItems () {
		// Argument-less constructor
	}
	
	/**
	 * Copy constructor.
	 * @param original the original object to duplicate.
	 */
	public ITSItems (ITSItems original) {
		for ( IITSItem item : original ) {
			add(item.createCopy());
		}
	}
	
	/**
	 * Returns an iterator for the items in this collection.
	 * @return the iterator.
	 */
	@Override
	public Iterator<IITSItem> iterator () {
		return map.values().iterator();
	}

	/**
	 * Adds (or sets) a new ITS data category instance in this collection.
	 * <ul>
	 * <li>If the data category is always single instance (like MT Confidence) this method
	 * replaces any existing instance by the new one.
	 * <li>If the data category allows for multiple instances (i.e. Provenance or Localization Quality Issue),
	 * this methods add the new instance. If an instance is already present, the existing instance is moved to a group
	 * and the new one is added to the group.  
	 * @param item the new item to set.
	 */
	public void add (IITSItem item) {
		if ( item instanceof LocQualityIssues ) {
			addSpecial((LocQualityIssues)item, true);
			return;
		}
		if ( item instanceof LocQualityIssue ) {
			addSpecial((LocQualityIssue)item);
			return;
		}
		if ( item instanceof Provenances ) {
			addSpecial((Provenances)item, true);
			return;
		}
		if ( item instanceof Provenance ) {
			addSpecial((Provenance)item);
			return;
		}
		// Else: it's a single instance case: just replace if needed
		map.put(item.getClass().getName(), item);
	}
	
	/**
	 * Indicates if there is at least one entry in this collection.
	 * @return true if there is at least one entry in this collection, false otherwise.
	 */
	public boolean isEmpty () {
		return map.isEmpty();
	}

	/**
	 * Removes all entries in this collection.
	 */
	public void clear () {
		map.clear();
	}
	
	/**
	 * Removes a given item from this collection.
	 * If the item is not found, nothing is done.
	 * For Provenance and Localization Quality Issue the item can be a group or an instance.
	 * @param item the item to remove.
	 */
	public void remove (IITSItem item) {
		IITSItem old = map.remove(item.getClass().getName());
		if ( old != null ) return; //Done
		// Else: For LocQualityIssue and Provenance the item may be a group
		DataCategoryGroup<?> group = null;
		if ( item instanceof LocQualityIssue ) {
			group = (DataCategoryGroup<?>)map.get(LocQualityIssues.class.getName());
		}
		if ( item instanceof Provenance ) {
			group = (DataCategoryGroup<?>)map.get(Provenances.class.getName());
		}
		if ( group != null ) {
			group.getList().remove(item);
		}
	}
	
	/**
	 * Gets the number of distinct data categories in this collection.
	 * Only one entry is counted for LocQualityIssue and Provenance, even if there are several.
	 * @return the number of distinct data categories in this collection.
	 */
	public int size () {
		return map.size();
	}

	/**
	 * Gets the item for a given data category name.
	 * This method calls {@link #get(Class)} after mapping the name to the class.
	 * @param dcName the name of the data category (as used in itsm:annotatorsRef).
	 * @return Same as for {@link #get(Class)}.
	 * @throws InvalidParameterException if the name is not valid.
	 */
	public IITSItem get (String dcName) {
		switch ( dcName ) {
		case DataCategories.LOCQUALITYISSUE:
			return get(LocQualityIssue.class);
		case DataCategories.PROVENANCE:
			return get(Provenance.class);
		case DataCategories.DOMAIN:
			return get(Domain.class);
		case DataCategories.TEXTANALYSIS:
			return get(TextAnalysis.class);
		case DataCategories.MTCONFIDENCE:
			return get(MTConfidence.class);
		}
		throw new InvalidParameterException("Unexpected data category name "+dcName);
	}
	
	/**
	 * Gets the item for a given type of data category.
	 * @param dcType the data category type.
	 * @return the item for the given type of data category, or null if there is none.
	 * The item returned is always a group for the data categories that may have more than a single instance
	 * (e.g. for multiple Provenance or Localization Quality Issue).
	 * That is: You always get a {@link Provenances} or a {@link LocQualityIssues} object (or null) 
	 * when you query for the classes {@link Provenance} or {@link LocQualityIssue}.
	 */
	public <A extends DataCategory> IITSItem get (Class<A> dcType) {
		IITSItem item = map.get(dcType.getName());
		if ( item == null ) {
			// For LocQualityIssue and Provenance the item may be a group
			if ( dcType == LocQualityIssue.class ) {
				return map.get(LocQualityIssues.class.getName());
			}
			if ( dcType == Provenance.class ) {
				return map.get(Provenances.class.getName());
			}
		}
		return item;
	}

	private void addSpecial (LocQualityIssues newGroup,
		boolean useNewGroupId)
	{
		// We should not have single instance of this data category
		LocQualityIssues oldGroup = (LocQualityIssues)map.get(LocQualityIssues.class.getName());
		if ( oldGroup == null ) {
			// No group existed, so we just set this new one
			map.put(LocQualityIssues.class.getName(), newGroup);
		}
		else {
			// Add the new items after the existing ones
			oldGroup.getList().addAll(newGroup.getList());
			if ( useNewGroupId ) {
				oldGroup.setGroupId(newGroup.getGroupId());
			}
		}
	}
	
	private void addSpecial (Provenances newGroup,
		boolean useNewGroupId)
	{
		// We should not have single instance of this data category
		Provenances oldGroup = (Provenances)map.get(Provenances.class.getName());
		if ( oldGroup == null ) {
			// No group existed, so we just set this new one
			map.put(Provenances.class.getName(), newGroup);
		}
		else {
			// Add the new items after the existing ones
			oldGroup.getList().addAll(newGroup.getList());
			if ( useNewGroupId ) {
				oldGroup.setGroupId(newGroup.getGroupId());
			}
		}
	}
	
	private void addSpecial (LocQualityIssue dc) {
		LocQualityIssues group = null;
		// LQI items are always in group (even single instance)
		// Do we have a group?
		IITSItem grpItem = map.get(LocQualityIssues.class.getName());
		if ( grpItem == null ) {
			// Create a group
			group = new LocQualityIssues(Util.createNCName());
			map.put(LocQualityIssues.class.getName(), group); // Add the new group
		}
		else { // Use the existing group
			group = (LocQualityIssues)grpItem;
		}
		// Add the new instance
		group.getList().add(dc);
	}

	private void addSpecial (Provenance dc) {
		Provenances group = null;
		// Provenance item are always in group (even single instance)
		// Do we have a group?
		IITSItem grpItem = map.get(Provenances.class.getName());
		if ( grpItem == null ) {
			// Create a group
			group = new Provenances(Util.createNCName());
			map.put(Provenances.class.getName(), group); // Add the new group
		}
		else { // Use the existing group
			group = (Provenances)grpItem;
		}
		// Add the new instance
		group.getList().add(dc);
	}

}
